package com.study.springbootsecurity.util;

import com.study.springbootsecurity.config.properties.JwtConfigProperties;
import com.study.springbootsecurity.config.redis.FastJsonRedisSerializer;
import io.lettuce.core.protocol.RedisStateMachine;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.core.BoundSetOperations;
/**
 * @Description: TODO
 * @author: Huangjianyun
 * @date: 2025-06-04 17:50
 */
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
@Slf4j
public class RedisCache {
    // 使用@RequiredArgsConstructor + 常量 等价于 @Autowired注解 注入redis模板类
    @Autowired
    private  RedisTemplate<Object, Object> redisTemplate;

    @Resource
    private  JwtConfigProperties jwtConfigProperties;

    @Autowired(required = false)
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        RedisSerializer stringSerializer = new StringRedisSerializer();
        FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(serializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(serializer);
        this.redisTemplate = redisTemplate;
    }
    /*
     * 检查redis 是否存在key
     * */
    public  boolean hasKey(final String key){
        return Optional.ofNullable(redisTemplate.hasKey(key)).orElse(false);
    }
    /**
     * 缓存基本对象（如 Integer、String、实体类等）
     * 使用默认过期时间（永不过期）
     *
     * @param key   缓存键
     * @param value 缓存值
     */
    public <T> void set(final String key,final T value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本对象并设置过期时间
     *
     * @param key      缓存键
     * @param value    缓存值
     * @param timeout  过期时间
     * @param timeUnit 时间单位
     */
    public <T> void set(final String key,final T value, final long timeout, final TimeUnit timeUnit){
        redisTemplate.opsForValue().set(key,value,timeout,timeUnit);
    }
    /**
     * 获取key的TTL
     *
     * @param key      缓存键
     * @return long 过期时间
     */
    public <T> T getTTL(final String key) {
        return (T)redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }


    /**
     * 设置缓存过期时间（默认单位：秒）
     *
     * @param key     缓存键
     * @param timeout 过期时间（秒）
     * @return true=设置成功，false=设置失败
     */
    public boolean expire(final String key, final long timeout) {
        return Optional.ofNullable(redisTemplate.expire(key,timeout,TimeUnit.SECONDS)).orElse(true);
    }


    /**
     * 设置缓存过期时间
     *
     * @param key     缓存键
     * @param timeout 过期时间
     * @param unit    时间单位
     * @return true=设置成功，false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit) {
        return Optional.ofNullable(redisTemplate.expire(key, timeout, unit)).orElse(true);
    }

    /**
     * 获取缓存的基本对象
     *
     * @param key 缓存键
     * @return 缓存值，若不存在则返回 null
     */
    public <T> T getCacheObject(final String key) {
        return (T)redisTemplate.opsForValue().get(key);
    }

    /**
     * 删除单个缓存对象
     *
     * @param key 缓存键
     * @return true=删除成功，false=删除失败
     */
    public boolean deleteObject(final String key) {
        return Optional.ofNullable(redisTemplate.delete(key)).orElse(true);
    }

    /**
     * 批量删除缓存对象
     *
     * @param collection 缓存键集合
     * @return 删除的数量
     */
    public long deleteObject(final Collection collection) {
        Long count = redisTemplate.delete(collection);
        return count == null ? 0 : count;
    }

    /**
     * 缓存 List 数据
     *
     * @param key      缓存键
     * @param dataList 待缓存的 List 数据
     * @return 缓存的元素数量
     */
    public <T> long setCacheList(final String key, final List<T> dataList) {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获取缓存的 List 数据
     *
     * @param key 缓存键
     * @return 缓存的 List 数据
     */
    public <T> List<T> getCacheList(final String key) {
        return (List<T>) redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存 Set 数据
     *
     * @param key     缓存键
     * @param dataSet 待缓存的 Set 数据
     * @return BoundSetOperations 对象，可继续操作该 Set
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
        // 获取原始的 BoundSetOperations 对象（键和值都是 Object 类型）
        BoundSetOperations<Object, Object> rawSetOperation = redisTemplate.boundSetOps(key);

        // 添加元素到集合中
        for (T item : dataSet) {
            // 将每个元素添加到集合中，使用 StringRedisSerializer 处理键，使用 redisTemplate 的值序列化器处理值
            rawSetOperation.add(item);
        }

        // 创建一个适配器，将原始的 BoundSetOperations<Object, Object> 转换为 BoundSetOperations<String, T>
        // 注意：这是一个不安全的类型转换，需要确保外部调用者正确处理类型
        @SuppressWarnings("unchecked")
        BoundSetOperations<String, T> typedSetOperation = (BoundSetOperations<String, T>) (BoundSetOperations<?, ?>) rawSetOperation;

        return typedSetOperation;
    }

    /**
     * 获取缓存的 Set 数据
     *
     * @param key 缓存键
     * @return 缓存的 Set 数据
     */
    public <T> Set<T> getCacheSet(final String key) {
        return (Set<T>) redisTemplate.opsForSet().members(key);
    }

    /**
     * 缓存 Map 数据
     *
     * @param key     缓存键
     * @param dataMap 待缓存的 Map 数据
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 获取缓存的 Map 数据
     *
     * @param key 缓存键
     * @return 缓存的 Map 数据
     */
    public <T> Map<String, T> getCacheMap(final String key) {
        // 强制转换键为 String，值为 T
        Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
        Map<String, T> result = new HashMap<>();
        for (Map.Entry<Object, Object> entry : entries.entrySet()) {
            String stringKey = (String) entry.getKey(); // 强制转换键为 String
            T value = (T) entry.getValue(); // 强制转换值为 T
            result.put(stringKey, value);
        }
        return result;
    }

    /**
     * 向 Hash 结构中存入数据
     *
     * @param key   Redis 键
     * @param hKey  Hash 键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 获取 Hash 结构中的数据
     *
     * @param key  Redis 键
     * @param hKey Hash 键
     * @return Hash 中的值
     */
    /**
     * 获取 Hash 结构中的数据
     *
     * @param key   Redis 键（需确保为 String 类型）
     * @param hKey  Hash 字段键（需确保为 String 类型）
     * @param targetType hash 字段value类型字节码
     * @return Hash 中的值（需根据实际类型强制转换）
     */
    @SuppressWarnings("unchecked") // 抑制类型转换警告
    public <T> T getCacheMapValue(final String key, final String hKey,Class<T> targetType) {
        // 获取原始 Hash 操作对象（键、字段、值均为 Object 类型）
        HashOperations<Object, Object, Object> rawOpsForHash = redisTemplate.opsForHash();

        // 从 Redis 中获取原始值（Object 类型）
        Object rawValue = rawOpsForHash.get(key, hKey);

        // 如果值存在，尝试转换为目标类型 T
        if (rawValue != null) {
            // 安全转换：确保值的类型与 T 兼容
            try {
                return (T) rawValue;
            } catch (ClassCastException e) {
                // 可根据需要添加日志记录或抛出异常
                log.error("获取redis的hashMap结果数据类型转换异常 {}: {}", targetType.getName(), rawValue, e);
            }
        }
        log.error("获取redis的hashMap结果获取目标值为null, rawValue:{}",  rawValue);
        return null; // 或根据需求返回默认值
    }
    /**
     * 删除 Hash 结构中的数据
     *
     * @param key  Redis 键
     * @param hKey Hash 键
     */
    public void delCacheMapValue(final String key, final String hKey) {
        HashOperations hashOperations = redisTemplate.opsForHash();
        hashOperations.delete(key, hKey);
    }

    /**
     * 批量获取 Hash 结构中的数据
     *
     * @param key   Redis 键
     * @param hKeys Hash 键集合
     * @return Hash 值集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
        return (List<T>) redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    /**
     * 获取匹配模式的所有缓存键
     *
     * @param pattern 匹配模式（如 "user:*"）
     * @return 匹配的缓存键集合
     */
    @SuppressWarnings("unchecked")
    public Collection<String> keys(final String pattern) {
        // 获取原始的键集合（类型为 Set<Object>）
        Set<Object> rawKeys = redisTemplate.keys(pattern);

        // 如果键集合为空，直接返回空集合
        if (rawKeys == null || rawKeys.isEmpty()) {
            return Collections.emptyList();
        }

        // 创建结果集合
        List<String> stringKeys = new ArrayList<>(rawKeys.size());

        // 遍历原始键集合，将每个键转换为字符串
        for (Object key : rawKeys) {
            if (key != null) {
                // 将键转换为字符串并添加到结果集合中
                stringKeys.add(key.toString());
            }
        }

        return stringKeys;
    }

    /*
     * 检查redis hashMap结构是否存在key
     * */
    public  boolean hasMapKey(final String key){
        return Optional.ofNullable(redisTemplate.opsForHash().getOperations().hasKey(key)).orElse(false);
    }

    /*
     * 保存token信息到redis,也可直接在创建token中使用该方法
     * */
    public void redisSaveTokenInfo(String token,String username){
        Map<String,Object> map = new HashMap<>();
        map.put("token",token);
        map.put("refreshTime",new Date(System.currentTimeMillis()+jwtConfigProperties.getRefreshExpiration()));
        map.put("expireTime",new Date(System.currentTimeMillis()+jwtConfigProperties.getExpiration()));
        expire(username,System.currentTimeMillis()+jwtConfigProperties.getRefreshExpiration(),TimeUnit.MILLISECONDS);
        setCacheMap(username,map);
    }
}
